package com.senyint.hip.fhir.provider;

import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r4.model.*;
import org.springframework.stereotype.Component;

import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;

/**
 * *******description*******
 * 患者
 * *************************
 *
 * @author ChenPan
 * @date 2018/10/29 10:31
 */
@Component
public class PatientProvider implements IResourceProvider {

    /**
     * 临时存储数据
     */
    private Map<Long, Deque<Patient>> patientRes = new HashMap<Long, Deque<Patient>>();
    /**
     * This is used to generate new IDs
     */
    private long myNextId = 1;

    @Override
    public Class<Patient> getResourceType() {
        return Patient.class;
    }

    @Search
    public List<Patient> findPatientsUsingArbitraryCtriteria() {
        LinkedList<Patient> retVal = new LinkedList<Patient>();

        for (Deque<Patient> nextPatientList : patientRes.values()) {
            Patient nextPatient = nextPatientList.getLast();
            retVal.add(nextPatient);
        }

        return retVal;
    }

    @Search()
    public List<Patient> findPatientsByName(@RequiredParam(name = "name") String patName) {
        LinkedList<Patient> retVal = new LinkedList<Patient>();

        for (Deque<Patient> nextPatientList : patientRes.values()) {
            Patient nextPatient = nextPatientList.getLast();
            NAMELOOP:
            for (HumanName nextName : nextPatient.getName()) {
                if (patName.equals(nextName.getText())) {
                    retVal.add(nextPatient);
                    break NAMELOOP;
                }
            }
        }

        return retVal;
    }
    
    @Search
    public List<Patient> findPatientsByName(@RequiredParam(name = "name") String patName, @RequiredParam(name ="_id") String id) {
        LinkedList<Patient> retVal = new LinkedList<Patient>();

        for (Deque<Patient> nextPatientList : patientRes.values()) {
            Patient nextPatient = nextPatientList.getLast();
           
            Long idPartAsLong = nextPatient.getIdElement().getIdPartAsLong();
           
            NAMELOOP:
            for (HumanName nextName : nextPatient.getName()) {
             
            	if(StringUtils.isNotEmpty(id)&&StringUtils.isNotEmpty(patName)) {
            		 if (patName.equals(nextName.getText())&&id.equals(idPartAsLong.toString())) {
                         retVal.add(nextPatient);
                         break NAMELOOP;
                     }
            	}
            	
            	if(StringUtils.isNotEmpty(id)) {
           		 if (patName.equals(nextName.getText())&&id.equals(idPartAsLong.toString())) {
                        retVal.add(nextPatient);
                        break NAMELOOP;
                    }
           	}
            	 
            	if(StringUtils.isNotEmpty(patName)) {
           		 if (patName.equals(nextName.getText())&&id.equals(idPartAsLong.toString())) {
                        retVal.add(nextPatient);
                        break NAMELOOP;
                    }
           	}
            }
        }

        return retVal;
    }
    
    
    @Search
    public List<Patient> findPatientsById(@RequiredParam(name ="_id") String id) {
        LinkedList<Patient> retVal = new LinkedList<Patient>();

        for (Deque<Patient> nextPatientList : patientRes.values()) {
            Patient nextPatient = nextPatientList.getLast();
           
            Long idPartAsLong = nextPatient.getIdElement().getIdPartAsLong();
           
            NAMELOOP:
            for (HumanName nextName : nextPatient.getName()) {
             
             
            	
            	if(StringUtils.isNotEmpty(id)) {
           		 if (id.equals(idPartAsLong.toString())) {
                        retVal.add(nextPatient);
                        break NAMELOOP;
                    }
           	}
            	 
            	 
            }
        }

        return retVal;
    }

    @Read(version = true)
    public Patient readPatient(@IdParam IdType theId) {
        Deque<Patient> retVal;
        try {
            retVal = patientRes.get(theId.getIdPartAsLong());
        } catch (NumberFormatException e) {
            /*
             * If we can't parse the ID as a long, it's not valid so this is an unknown resource
             */
            throw new ResourceNotFoundException(theId);
        }

        if (theId.hasVersionIdPart() == false) {
            return retVal.getLast();
        } else {
            for (Patient nextVersion : retVal) {
                String nextVersionId = nextVersion.getIdElement().getVersionIdPart();
                if (theId.getVersionIdPart().equals(nextVersionId)) {
                    return nextVersion;
                }
            }
            // No matching version
            throw new ResourceNotFoundException("Unknown version: " + theId.getValue());
        }

    }

    @History()
    public Deque<Patient> historyPatient(@IdParam IdType theId) {
        Deque<Patient> retVal;
        try {
            retVal = patientRes.get(theId.getIdPartAsLong());
        } catch (NumberFormatException e) {
            /*
             * If we can't parse the ID as a long, it's not valid so this is an unknown resource
             */
            throw new ResourceNotFoundException(theId);
        }
        return retVal;
    }

    @Create()
    public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
        validateResource(thePatient);

        // Here we are just generating IDs sequentially
        long id = myNextId++;

        String versionId = addNewVersion(thePatient, id);

        MethodOutcome retVal = new MethodOutcome();
        retVal.setId(new IdType("Patient", String.valueOf(id), versionId));

        OperationOutcome outcome = new OperationOutcome();
        outcome.setIdElement(new IdType("Patient", String.valueOf(id), versionId));
        outcome.addIssue().setDiagnostics("新增患者成功").setId(String.valueOf(id));
        retVal.setOperationOutcome(outcome);

        return retVal;
    }

    @Update()
    public MethodOutcome updatePatient(@IdParam IdType theId, @ResourceParam Patient thePatient) {
        validateResource(thePatient);

        Long id;
        try {
            id = theId.getIdPartAsLong();
        } catch (DataFormatException e) {
            throw new InvalidRequestException("Invalid ID " + theId.getValue() + " - Must be numeric");
        }

        /*
         * Throw an exception (HTTP 404) if the ID is not known
         */
        if (!patientRes.containsKey(id)) {
            throw new ResourceNotFoundException(theId);
        }

        String versionId = addNewVersion(thePatient, id);

        MethodOutcome retVal = new MethodOutcome();
        retVal.setId(new IdType("Patient", String.valueOf(id), versionId));
        OperationOutcome outcome = new OperationOutcome();
        outcome.setIdElement(new IdType("Patient", String.valueOf(id), versionId));
        outcome.addIssue().setDiagnostics("更新患者成功").setId(String.valueOf(id));
        retVal.setOperationOutcome(outcome);

        return retVal;
    }

    @Delete
    public MethodOutcome deletePatient(@IdParam IdType theId) {
        Long id;
        try {
            id = theId.getIdPartAsLong();
        } catch (DataFormatException e) {
            throw new InvalidRequestException("Invalid ID " + theId.getValue() + " - Must be numeric");
        }
        /*
         * Throw an exception (HTTP 404) if the ID is not known
         */
        if (!patientRes.containsKey(id)) {
            throw new ResourceNotFoundException(theId);
        }

        patientRes.remove(id);

        MethodOutcome retVal = new MethodOutcome();
        OperationOutcome outcome = new OperationOutcome();
        outcome.addIssue().setDiagnostics("删除患者成功").setId(String.valueOf(id));
        retVal.setOperationOutcome(outcome);

        return retVal;
    }

    private String addNewVersion(Patient thePatient, Long theId) {
        Date publishedDate;
        if (!patientRes.containsKey(theId)) {
            patientRes.put(theId, new LinkedList<Patient>());
            publishedDate = InstantDt.withCurrentTime().getValue();
        } else {
            Patient currentPatitne = patientRes.get(theId).getLast();
            publishedDate = currentPatitne.getMeta().getLastUpdated();
        }

        /*
         * PUBLISHED time will always be set to the time that the first version was stored. UPDATED time is set to the time that the new version was stored.
         */
        thePatient.getMeta().setLastUpdated(publishedDate);
        thePatient.getMeta().setLastUpdated(InstantDt.withCurrentTime().getValue());
        Deque<Patient> existingVersions = patientRes.get(theId);

        // We just use the current number of versions as the next version number
        String newVersion = Integer.toString(existingVersions.size());

        // Create an ID with the new version and assign it back to the resource
        IdType newId = new IdType("Patient", Long.toString(theId), newVersion);
        thePatient.setId(newId);

        existingVersions.add(thePatient);
        return newVersion;
    }

    private void validateResource(Patient thePatient) {
        /*
         * Our server will have a rule that patients must have a family name or we will reject them
         */
        if (thePatient.getNameFirstRep().isEmpty()) {
            OperationOutcome outcome = new OperationOutcome();
            outcome.addIssue().setSeverity(OperationOutcome.IssueSeverity.FATAL).
                    setDetails(new CodeableConcept().setText("No name provided, Patient resources must have name."));
            throw new UnprocessableEntityException("Unprocessable Entity", outcome);
        }
    }

}
